package com.refusea.commons.utils

import com.refusea.commons.net.LocalHost
import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.atomic.AtomicInteger
import org.apache.commons.codec.binary.Hex
import org.apache.commons.codec.binary.Base64

/**
 * 返回一个基于本机网络地址和当前时间的唯一流水号
 *
 * @author Zhang Yan
 */
object Sn {

    // 时间偏移量, 2020-1-1
    private const val OFFSET = 1577808000000L

    private val sequence: AtomicInteger = AtomicInteger()
    private val ip = ip()

    /** 返回 28 位序列号  */
    @JvmStatic
    fun next(): String {
        return next(System.currentTimeMillis())
    }

    /** 返回 32 位序列号  */
    @JvmStatic
    fun next32(): String {
        return next32(System.currentTimeMillis())
    }

    /** 返回 base64 序列号  */
    @JvmStatic
    fun next64(): String {
        return next64(System.currentTimeMillis())
    }

    /** @param time 当前时间戳
     */
    @JvmStatic
    fun next(time: Long): String {
        val data = ByteArray(14)
        System.arraycopy(ip, 0, data, 0, 4)
        // 时间戳尚不足以占用 Long 的全部 8 个字节, 其高位 2 个字节是空闲的, 可以忽略
        System.arraycopy(toByteArray(time), 2, data, 4, 6)
        System.arraycopy(toByteArray(sequence.getAndIncrement()), 0, data, 10, 4)
        return Hex.encodeHexString(data)
    }

    /** 返回 32 位 id  */
    @JvmStatic
    fun next32(time: Long): String {
        val data = ByteArray(16)
        System.arraycopy(ip, 0, data, 0, 4)
        // 时间戳尚不足以占用 Long 的全部 8 个字节, 其高位 2 个字节是空闲的, 可以忽略
        System.arraycopy(toByteArray(time), 0, data, 4, 8)
        System.arraycopy(toByteArray(sequence.getAndIncrement()), 0, data, 12, 4)
        return Hex.encodeHexString(data)
    }

    /** 返回 base64 id  */
    @JvmStatic
    fun next64(time: Long): String {
        val data = ByteArray(14)
        System.arraycopy(ip, 0, data, 0, 4)
        // 时间戳尚不足以占用 Long 的全部 8 个字节, 其高位 2 个字节是空闲的, 可以忽略
        System.arraycopy(toByteArray(time), 2, data, 4, 6)
        System.arraycopy(toByteArray(sequence.getAndIncrement()), 0, data, 10, 4)
        return Base64.encodeBase64URLSafeString(data)
    }

    private fun ip(): ByteArray {
        return if (LocalHost.getIpv4() == null) toByteArray(
            ThreadLocalRandom.current().nextInt()
        ) else LocalHost.getAddress()
    }

    /** 生成 16 位 id: ip 地址后 2 字节 + 5 字节时间偏移量 + 1 字节数字循环  */
    @JvmStatic
    fun next16(): String {
        return next16(System.currentTimeMillis())
    }

    @JvmStatic
    fun next16(now: Long): String {
        val data = ByteArray(8)
        System.arraycopy(ip, 2, data, 0, 2)
        System.arraycopy(toByteArray(now - OFFSET), 3, data, 2, 5)
        System.arraycopy(toByteArray(sequence.getAndIncrement()), 3, data, 7, 1)
        return Hex.encodeHexString(data)
    }

    private fun toByteArray(value: Long): ByteArray {
        // Note that this code needs to stay compatible with GWT, which has known
        // bugs when narrowing byte casts of long values occur.
        var v = value
        val result = ByteArray(8)
        for (i in 7 downTo 0) {
            result[i] = (v and 0xffL).toByte()
            v = v shr 8
        }
        return result
    }

    private fun toByteArray(value: Int): ByteArray {
        return byteArrayOf((value shr 24).toByte(), (value shr 16).toByte(), (value shr 8).toByte(), value.toByte())
    }

}
